/*
 * IMLocalStorageM.nc -- local storage component
 *
 * Copyright (C) Andrey Vihrov <andrey.vihrov@gmail.com>, 2010
 * Copyright (C) Askar Salimbaev <ghostinshell@gmail.com>, 2010
 *
 * This file is part of Intermote.
 *
 * Intermote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Intermote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Intermote.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <Intermote.h>

module IMBlockStorageM {

	provides {
		interface IMBlockStorage as BlockStorage;
		interface IMBlockProperty as BlockProperty[uint8_t];
		interface Init;
	}
	uses {
		interface BlockRead as BlockRead;
		interface IMReplication as Replication;
		interface IMBlockWriteExt as BlockWrite;
		interface Queue<queuedRequest_t>;
		interface IMLocalAddress as LocalAddress;
		interface IMBlockId as BlockId;
	}

}
implementation {

	typedef enum {
		S_ALLOC,
		S_READ,
		S_RESERVE,
		S_PROPERTY,
		S_WRITE,
		S_ERASE,
	} LocalStorageReq_t;

	uint32_t buffer;
	uint8_t property;
	LocalStorageReq_t state;
	const uint8_t allocationMark=0;
	im_block_id_t blockCount=0;
	im_block_id_t nextBlock=0;
	im_block_id_t allocBlock=0;
	bool initDone = 0;
	bool globalLock = 0;

	/* Useful functions */
	storage_addr_t getPhysicalAddress(im_block_id_t block) {
		// calculate physical address
		return (block << IM_BLOCK_SIZE_LOG2);
	}
	storage_addr_t getPropAddress(im_block_id_t block,  uint8_t prop) {
		// calculate property address
		return getPhysicalAddress(block) + 1 + (4 * prop);
	}
	storage_addr_t getDataAddress(im_block_id_t block, storage_addr_t offset) {
		// calculate block data address
		return getPhysicalAddress(block) + offset + (4 * IM_PROP_COUNT + 1);
	}


	/* BlockStorage interface operations */

	command error_t Init.init () {
		storage_len_t size;
		if(initDone) {
			return SUCCESS; // Don't initialize twice
		}
		size = call BlockRead.getSize();
		blockCount = (size >> IM_BLOCK_SIZE_LOG2);
		initDone = 1;
		return SUCCESS;
	}

	command	error_t BlockStorage.allocate(void) {
		state = S_ALLOC;
		allocBlock = nextBlock;
		call BlockRead.read(getPhysicalAddress(nextBlock),&buffer,1);
		return SUCCESS;
	}

	command error_t BlockStorage.erase (im_block_id_t block) {
		state = S_ERASE;
		call BlockWrite.erase(block);
		// Erase replicated blocks
		call Replication.erase(block);
		return SUCCESS;
	}

	command error_t BlockStorage.read(im_block_id_t block, im_off_t offset, struct im_chunk *buf) {
		return call BlockRead.read(getDataAddress(block, offset), buf->data, IM_CHUNK_SIZE);
	}

	command error_t BlockStorage.write(im_block_id_t block, im_off_t offset, const struct im_chunk *buf) {
		state = S_WRITE;
		call BlockWrite.write(getDataAddress(block, offset), (void*)buf->data, IM_CHUNK_SIZE);
		// Replicates changes
		call Replication.write(block, offset, (void*)buf->data);
		return SUCCESS;
	}

	command error_t BlockStorage.reserve(uint8_t count) {
		// TODO: To be implemented
		return FAIL;
	}

	/* BlockProperty interface operations */

	command error_t BlockProperty.set[uint8_t prop] (im_block_id_t block, uint32_t data) {
		state = S_PROPERTY;
		property = prop;
		buffer = data;
		return call BlockWrite.write(getPropAddress(block,prop), &buffer, 4);
	}

	command error_t BlockProperty.get[uint8_t prop] (im_block_id_t block) {
		state = S_PROPERTY;
		property = prop;
		return call BlockRead.read(getPropAddress(block,prop), &buffer, 4);
	}

	default event void BlockProperty.setDone[uint8_t prop] (error_t err) {}

	default event void BlockProperty.getDone[uint8_t prop] (error_t err, uint32_t data) {}

	/* Block read\write events */
	event void BlockWrite.writeDone(storage_addr_t addr, void* buf, storage_len_t len, error_t error) {
		switch(state) {
			case S_ALLOC:
				// Update next block on success
				nextBlock=nextBlock+1;
				if(nextBlock == blockCount) {
					nextBlock=0;
				}
				signal BlockStorage.allocateDone(error,
				call BlockId.create(call LocalAddress.address(), allocBlock % blockCount));
				break;
			case S_WRITE:
				signal BlockStorage.writeDone(error);
				break;
			case S_PROPERTY:
				signal BlockProperty.setDone[property](error);
				break;
			default:
		}
	}

	event void BlockWrite.syncDone(error_t error) {
	}

	event void BlockWrite.eraseDone(error_t error) {
		signal BlockStorage.eraseDone(error);
	}

	event void BlockRead.readDone(storage_addr_t addr, void* buf, storage_len_t len, error_t error) {
		switch(state) {
			case S_ALLOC:
				// Check allocation mark
				if(((uint8_t*)buf)[0] != allocationMark) { // If block is free
					// Write allocation mark
					call BlockWrite.write(getPhysicalAddress(allocBlock%blockCount),
						(void*)&allocationMark,1);
				} else {
					// Continue search
					allocBlock = allocBlock + 1;
					if(allocBlock % blockCount  == nextBlock) {
						// No more free storage
						signal BlockStorage.allocateDone(FAIL, 0);
						return;
					}

					// Try next block
					call BlockRead.read(getPhysicalAddress(allocBlock%blockCount),
						&buffer,1);
				}
				break;
			case S_READ:
				signal BlockStorage.readDone(error);
				break;
			case S_PROPERTY:
				// Returns property data
				signal BlockProperty.getDone[property](error,buffer);
				break;
			default:
		}
	}

	event void BlockRead.computeCrcDone(storage_addr_t addr, storage_len_t len,
		uint16_t crc, error_t error) {
	}
}
